{		Miscellaneous routines used with the serial port XCMDs.

	This file is included in the other serial port XCMD source code files -- i.e., it is not compiled and
	linked separately.

	Copyright © 1987,88 Apple Computer, Inc.

	Initial coding 9/87 by Harry Chesley.
}

const

GLOBALNAME = 'SPortGlobals';			{ Where to store a handle to our globals. }
MAXPARMBLKS = 4;							{ Maximum number of outgoing parameter blocks per port. }
WRAPCOLUMN = 75;							{ The column at which to autowrap, if we are autowrapping. }
MAXWRAP = 30;								{ The maximum number of characters to carry forward when autowrapping. }

type

{ Serial port global data: }
PortDataType =
	record
		isOpen: boolean;								{ Whether the port is open yet. }
		portInDev, portOutDev: integer;		{ The device numbers for input and output. }
		byteConfig: integer;							{ The byte format being used. }
		shakes: SerShk;								{ Flow control, etc. }
		sendLFs: boolean;								{ Whether to send linefeeds after carriage returns. }
		doEcho: boolean;								{ Whether to echo input. }
		doEdit: boolean;								{ Whether to allow backspace editing on input. }
		stripIncoming: boolean;						{ Whether to strip the top bit on incoming bytes. }
		stripControls: boolean;						{ Whether to strip out non-return, non-tab control characters. }
		autoWrap: boolean;							{ Auto-wrap input and output. }
		currentColumn: integer;					{ Column we're about to write into. }
		inputBuffer: Ptr;								{ The input buffer, or nil if there is none allocated. }
		parmBlks: array [1..MAXPARMBLKS] of ParmBlkPtr;	{ Asynch IO blocks (for output). }
	end;

OurGlobalHandle = ^OurGlobalPtr;
OurGlobalPtr = ^OurGlobalType;

{ Global data: }
OurGlobalType =
	record
		selectedPort: SPortSel;								{ Which port is selected. }
		ports: array [SPortSel] of PortDataType;		{ The port data for each port. }
	end;

var

{ Global data. (Note: Although this is called global, it's actually allocated as local to the top-level
	XCMD routine, and therefore only stays around for the duration of the XCMD execution. Being
	able to think of it as globals, and not having to pass it to each subroutine called, however, is
	extremely useful. This is perhaps the best reason for using Pascal rather than C for writing
	XCMDs.) }

Globals: OurGlobalHandle;		{ Global data (this handle is saved in a HyperTalk global between XCMDs). }
ThisSPort: PortDataType;		{ A temporary copy of the global data for the currently selected port. }

procedure GetStrGlobal(name: str255; var glob: str255);
	{ Set glob to the global string specified by name. }

	var globHand: Handle;

	begin
		{ Get the HyperTalk global. }
		globHand := GetGlobal(name);
		{ Convert it to a Pascal string. }
		if globHand = nil then glob := ''
		else
			begin
				ZeroToPas(globHand^,glob);
				DisposHandle(globHand);
			end;
	end;

function GetLongGlobal(name: str255): longInt;
	{ Return the global string specified by name, interpreted as a long integer. }

	var globStr: str255;

	begin
		{ Get the HyperTalk global into a Pascal string. }
		GetStrGlobal(name,globStr);
		{ Convert it to a longInt. }
		GetLongGlobal := StrToLong(globStr);
	end;

procedure SetStrGlobal(name: str255; glob: str255);
	{ Set the global string specified by name to glob. }

	var globHand: Handle;

	begin
		{ Convert the string to a HyperTalk style handle-string. }
		globHand := PasToZero(glob);
		{ Set the global. }
		SetGlobal(name,globHand);
		{ Dispose of our copy. }
		DisposHandle(globHand);
	end;

procedure SetLongGlobal(name: str255; globLong: longInt);
	{Set the global string specified by name to a string that represents the number in globLong. }

	var globStr: str31;

	begin
		{ Convert the longInt to a Pascal string. }
		globStr := LongToStr(globLong);
		{ Set the HyperTalk global to that. }
		SetStrGlobal(name,globStr);
	end;

procedure SetUpSPortGlobals;
	{ Create the handle we use for our global variables, and remember it in a HyperCard global. }

	var portIndex: SPortSel;

	begin
		{ Get the value of the global that holds our globals handle. }
		Globals := OurGlobalHandle(GetLongGlobal(GLOBALNAME));
		{ If it's empty (which will evaluate to zero or nil), then we need to create it. }
		if Globals = nil then
			begin
				{ Make the handle. }
				Globals := OurGlobalHandle(NewHandle(sizeof(OurGlobalType)));
				if Globals = nil then Fail('could not allocate global variable space');
				{ Default to modem port. }
				Globals^^.selectedPort := sPortA;
				{ Mark both ports as closed. }
				for portIndex := sPortA to sPortB do
					with Globals^^.ports[portIndex] do
						begin
							isOpen := false;
							{ Set the device numbers. }
							case portIndex of
								sPortA:
									begin portInDev := -6; portOutDev := -7; end;
								sPortB:
									begin portInDev := -8; portOutDev := -9; end;
								end;
							{ Set the default byte format. }
							byteConfig := baud1200+stop10+noParity+data8;
							{ Set the rest of the port defaults. }
							with shakes do
								begin
									fXOn := 0; fCTS := 0; errs := 0; evts := 0; fInX := 0;
								end;
							{ Set no auto-linefeed, no echo, no edit, no strip top bits. }
							sendLFs := false;
							doEcho := false;
							doEdit := false;
							stripIncoming := true;
							stripControls := false;
							autoWrap := false;
							currentColumn := 1;
							{ No input buffer (i.e., use the default port buffer). }
							inputBuffer := nil;
						end;
				{ Store our globals handle in the HyperTalk global, so we can get it later. }
				SetLongGlobal(GLOBALNAME,ord4(Globals));
			end;
		{ Copy the globals for the active port into a more easily accessed location. }
		ThisSPort := Globals^^.ports[Globals^^.selectedPort];
	end;

procedure EnsureOpenPort;
	{ Check if the port is open. If it isn't, then open it now. }

	var i: integer;
		actualIn, actualOut: integer;
		whichPort: SPortSel;

	begin
		if not ThisSPort.isOpen then
			begin
				{ Get the port. }
				whichPort := Globals^^.selectedPort;

				{ Allocate and init a gaggle of parmBlks for use with asynchronous output. }
				for i := 1 to MAXPARMBLKS do
					begin
						Globals^^.ports[whichPort].parmBlks[i] := ParmBlkPtr(NewPtr(sizeof(ParamBlockRec)));
						with Globals^^.ports[whichPort].parmBlks[i]^ do
							begin
								ioCompletion := nil;
								ioResult := noErr;
								ioRefNum := Globals^^.ports[whichPort].portOutDev;
								ioBuffer := nil;
							end;
					end;
				{ Copy the new info into the current port. }
				ThisSPort := Globals^^.ports[whichPort];

				{ Actually open the port. }
				if Globals^^.selectedPort = sPortA then
					begin
						if (OpenDriver('.AOut',actualOut) <> noErr) or (OpenDriver('.AIn',actualIn) <> noErr) then
							Fail('OpenDriver failed');
					end
				else
					begin
						if (OpenDriver('.BOut',actualOut) <> noErr) or (OpenDriver('.BIn',actualIn) <> noErr) then
							Fail('OpenDriver failed');
					end;
				with ThisSPort do
					begin
						{ Be paranoid about making sure everything is opened properly. }
						if (actualOut <> portOutDev) or (actualIn <> portInDev) then Fail('openDriver failed');
						{ Set the defaults. }
						if SerHShake(portInDev,shakes) <> noErr then Fail('SerHShake failed');
						if SerHShake(portOutDev,shakes) <> noErr then Fail('SerHShake failed');
						if (SerReset(portInDev,byteConfig) <> noErr) or (SerReset(portOutDev,byteConfig) <> noErr) then
							Fail('SerReset failed');
						Globals^^.ports[Globals^^.selectedPort].isOpen := true;
						isOpen := true;
					end;
			end;
	end;

procedure GetStrParm(n: integer; var str: str255);
	{ Get the nth parameter into str. }

	begin
		ZeroToPas(paramPtr^.params[n]^,str);
	end;

function GetLongParm(n: integer): longInt;
	{ Return the nth parameter string, interpreted as a long integer. }

	var str: str255;

	begin
		ZeroToPas(paramPtr^.params[n]^,str);
		GetLongParm := StrToNum(str);
	end;

function FindFreeIOBlk: ParmBlkPtr;
	{ Find and return a free I/O parameter block. If none are free, return nil. }

	var i: integer;

	begin
	for i := 1 to MAXPARMBLKS do
		if ThisSPort.parmBlks[i]^.ioResult <= noErr then
			begin
				FindFreeIOBlk := ThisSPort.parmBlks[i];
				exit(FindFreeIOBlk);
			end;
	FindFreeIOBlk := nil;
	end;

function WaitForFreeIOBlk: ParmBlkPtr;
	{ Wait for an I/O block to be free and return it. }

	var theBlock: ParmBlkPtr;

	begin
		repeat theBlock := FindFreeIOBlk until theBlock <> nil;
		WaitForFreeIOBlk := theBlock;
	end;

procedure WaitForAllFree;
	{ Wait for all I/O blocks to free up (i.e., wait for all output to finish). }

	var allDone: boolean;
		i: integer;

	begin
		repeat
			allDone := true;
			for i := 1 to MAXPARMBLKS do
				if ThisSPort.parmBlks[i]^.ioResult > noErr then allDone := false;
		until allDone;
	end;
